/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tzpay.tzpay;


import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Type;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okio.BufferedSink;
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.POST;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

public final class ChunkingConverter {
    @Target(PARAMETER)
    @Retention(RUNTIME)
    @interface Chunked {
    }

    public static class ChunkingConverterFactory extends Converter.Factory {
        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                              Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            boolean isBody = false;
            boolean isChunked = false;
            for (Annotation annotation : parameterAnnotations) {
                isBody |= annotation instanceof Body;
                isChunked |= annotation instanceof Chunked;
            }
            if (!isBody || !isChunked) {
                return null;
            }

            // Look up the real converter to delegate to.
            final Converter<Object, RequestBody> delegate =
                    retrofit.nextRequestBodyConverter(this, type, parameterAnnotations, methodAnnotations);
            // Wrap it in a Converter which removes the content length from the delegate's body.
            return new Converter<Object, RequestBody>() {
                @Override
                public RequestBody convert(Object value) throws IOException {
                    final RequestBody realBody = delegate.convert(value);
                    return new RequestBody() {
                        @Override
                        public MediaType contentType() {
                            return realBody.contentType();
                        }

                        @Override
                        public void writeTo(BufferedSink sink) throws IOException {
                            realBody.writeTo(sink);
                        }
                    };
                }
            };
        }
    }

    static class Repo {
        final String owner;
        final String name;

        Repo(String owner, String name) {
            this.owner = owner;
            this.name = name;
        }
    }

    interface Service {
        @POST("/")
        Call<ResponseBody> sendNormal(@Body Repo repo);

        @POST("/")
        Call<ResponseBody> sendChunked(@Chunked @Body Repo repo);
    }

    public static void main(String... args) throws IOException, InterruptedException {
        MockWebServer server = new MockWebServer();
        server.enqueue(new MockResponse());
        server.enqueue(new MockResponse());
        server.start();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(server.url("/"))
                .addConverterFactory(new ChunkingConverterFactory())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        Service service = retrofit.create(Service.class);

        Repo retrofitRepo = new Repo("square", "retrofit");

        service.sendNormal(retrofitRepo).execute();
        RecordedRequest normalRequest = server.takeRequest();
        System.out.println(
                "Normal @Body Transfer-Encoding: " + normalRequest.getHeader("Transfer-Encoding"));

        service.sendChunked(retrofitRepo).execute();
        RecordedRequest chunkedRequest = server.takeRequest();
        System.out.println(
                "@Chunked @Body Transfer-Encoding: " + chunkedRequest.getHeader("Transfer-Encoding"));

        server.shutdown();
    }


}
